/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/models/db/media_file_table.h>
#include <journal/models/db/common_table.h>

using namespace std;
using namespace journal::model::db;

media_file_table::media_file_table()
{
}

std::shared_ptr<imedia_file_table> media_file_table::create(std::shared_ptr<s::iengine> dbEngine)
{
    std::shared_ptr<media_file_table> returnValue = make_shared<media_file_table>();
    returnValue->_commonTableClient = std::dynamic_pointer_cast<icommon_table_client>(returnValue);
    returnValue->_commonTable = createCommonTable(returnValue->_commonTableClient, dbEngine, L"MediaFile");
    return returnValue;
}

std::shared_ptr<icommon_table> media_file_table::common()
{
    return _commonTable;
}

void media_file_table::defineColumns(std::shared_ptr<s::itable> table)
{
    BAJA_LOCK(_mutex);

    _mediaFileIdColumn = s::createColumn<b::uint32>(L"ID", s::column_type::type_int32);
    table->addColumn(_mediaFileIdColumn);

    _fileNameColumn = s::createStringColumn(L"FileName", s::column_type::type_string_long);
    _fileNameColumn->setFixed(false);
    table->addColumn(_fileNameColumn);

    _originalFileNameColumn = s::createStringColumn(L"OriginalFileName", s::column_type::type_string_long);
    _originalFileNameColumn ->setFixed(false);
    table->addColumn(_originalFileNameColumn);
}

void media_file_table::defineIndexColumns(std::shared_ptr<s::iindex> primaryKeyIndex)
{
    BAJA_LOCK(_mutex);

    // PK index...
    _mediaFileIdIndexColumn = s::createIndexColumnDefinition(_mediaFileIdColumn, true);
    primaryKeyIndex->addIndexColumn(_mediaFileIdIndexColumn);

    // original file name index
    _originalFileNameIndexColumn = s::createIndexColumnDefinition(_originalFileNameColumn, true);

    _originalFileNameIndex = s::createIndex(L"OriginalFileName_Index", false, true);
    _originalFileNameIndex->addIndexColumn(_originalFileNameIndexColumn);

    _commonTable->tableDefinition()->addIndex(_originalFileNameIndex);
}

void media_file_table::defineRowCells(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> row)
{
    BAJA_LOCK(_mutex);

    _mediaFileIdCell = _mediaFileIdColumn->createCell(0);
    _fileNameCell = _fileNameColumn->createCell(L"");
    _originalFileNameCell = _originalFileNameColumn->createCell(L"");

    row->push_back(_mediaFileIdCell);
    row->push_back(_fileNameCell);
    row->push_back(_originalFileNameCell);
}

void media_file_table::insert(b::uint32 mediaFileId,
                              std::wstring fileName,
                              std::wstring originalFileName)
{
    BAJA_LOCK(_mutex);

    _mediaFileIdCell->setValue(mediaFileId);
    _fileNameCell->setValue(fileName);
    _originalFileNameCell->setValue(originalFileName);

    _commonTable->insert(_commonTable->stagingRow());
}

void media_file_table::load(
    b::uint32 mediaFileId,
    std::wstring& fileName,
    std::wstring& originalFileName)
{
    BAJA_LOCK(_mutex);

    // select all columns
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_mediaFileIdColumn);
    result->addColumn(_originalFileNameColumn);
    result->addColumn(_fileNameColumn);
    // where id = mediaFileId

    // execute
    read(mediaFileId, result);

    // iterator through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    BAJA_ASSERT(rows->size() == 1, L"Either 0 or 1 records is expected.");

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();

        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            if( cell->getColumnId() == _mediaFileIdColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                if (mediaFileId == typedCell->getValue())
                {
                    continue;
                }
                else
                {
                    break;
                }
            }
            if( cell->getColumnId() == _originalFileNameColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<std::wstring>> typedCell = std::dynamic_pointer_cast<s::icell_typed<std::wstring>>(cell);
                originalFileName = typedCell->getValue();
            }
            if( cell->getColumnId() == _fileNameColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<std::wstring>> typedCell = std::dynamic_pointer_cast<s::icell_typed<std::wstring>>(cell);
                fileName = typedCell->getValue();
            }
        }
    }
}

bool media_file_table::findMediaFile(
    std::wstring originalFileName,
    b::uint32& mediaFileId,
    std::wstring& localMediaFileName)
{
    BAJA_LOCK(_mutex);

    // select ids...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_mediaFileIdColumn);
    result->addColumn(_fileNameColumn);

    // where original name in originalFileName
    readOriginalFileName(originalFileName, result);

    // iterator through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    if (rows->size() == 0)
    {
        return false;
    }

    // There should only be one and only one match.
    BAJA_ASSERT(rows->size() == 1, L"Either 0 or 1 records is expected.");

    b::uint32 readMediaFileId;
    std::wstring readFileName;

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        b::uint32 validCellCount = 0;
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();

        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            if( cell->getColumnId() == _mediaFileIdColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                readMediaFileId = typedCell->getValue();
                continue;
            }
            if( cell->getColumnId() == _fileNameColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<std::wstring>> typedCell = std::dynamic_pointer_cast<s::icell_typed<std::wstring>>(cell);
                readFileName = typedCell->getValue();
                continue;
            }
        }
    }

    mediaFileId = readMediaFileId;
    localMediaFileName = readFileName;

    return true;
}

void media_file_table::read(b::uint32 mediaFileId,
                            std::shared_ptr<s::iresult> result)
{
    BAJA_LOCK(_mutex);

    _mediaFileIdCell->setValue(mediaFileId);

    _commonTable->read(_commonTable->stagingRow(),
                        static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()),
                       result);
}

void media_file_table::readOriginalFileName(std::wstring originalFileName,
                                            std::shared_ptr<s::iresult> result)
{
    BAJA_LOCK(_mutex);

    _originalFileNameCell->setValue(originalFileName);

    _commonTable->read(_originalFileNameIndex,
                       _commonTable->stagingRow(),
                       static_cast<baja::uint32>(_originalFileNameIndex->getIndexColumns()->size()),
                       true,
                       result);
}

void media_file_table::readIds(
    std::set<b::uint32>& mediaFileIds)
{
    BAJA_LOCK(_mutex);

    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_mediaFileIdColumn);

    // execute
    this->common()->readAll(result);

    // iterator through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    for (auto &row : *rows)
    {
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        for (auto &cell : *cells)
        {
            BAJA_ASSERT( cell->getColumnId() == _mediaFileIdColumn->getId(), L"result contains unexpected columns");

            std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
            mediaFileIds.insert(typedCell->getValue());
        }
    }
}

void media_file_table::dispose(
    const b::uint32& mediaFileId)
{
    BAJA_LOCK(_mutex);

    _mediaFileIdCell->setValue(mediaFileId);

    _commonTable->dispose(_commonTable->stagingRow());
}

void media_file_table::disposeUnused(
    const std::set<b::uint32>& inUseSet,
    std::vector<std::wstring>& filesToDispose)
{
    BAJA_LOCK(_mutex);

    std::set<b::uint32> setToDispose;
    findIdComplementSet(inUseSet, setToDispose);

    for (auto &id : setToDispose)
    {
        // read info
        std::wstring fileName;
        std::wstring originalFileName;
        load(id, fileName, originalFileName);

        filesToDispose.push_back(fileName);

        // delete record
        dispose(id);
    }
}

void media_file_table::findIdComplementSet(
    const std::set<b::uint32>& subSet,
    std::set<b::uint32>& subSetComplement)
{
    BAJA_LOCK(_mutex);

    std::set<b::uint32> mediaFileIds;
    readIds(mediaFileIds);

    std::vector<b::uint32> difference(mediaFileIds.size());
    std::vector<b::uint32>::iterator last = std::set_difference(mediaFileIds.begin(), mediaFileIds.end(), subSet.begin(), subSet.end(), difference.begin());
    for (std::vector<b::uint32>::iterator c = difference.begin(); c != last; ++c)
    {
        subSetComplement.insert(*c);
    }
}

std::shared_ptr<imedia_file_table> journal::model::db::createMediaFileTable(std::shared_ptr<s::iengine> dbEngine)
{
    return media_file_table::create(dbEngine);
}
